<template>
  <div id="app">
    <div class="side-bar">
      <div>
        <label>选择器前缀</label>
        <input type="text" v-model="prefix" />
      </div>
      <div>
        <label>颜色替换</label>
      </div>
      <div type="color" v-for="c in colorGroups" :key="c.hsl">
        <div>
          <span
            class="color-block"
            :style="{
              background: c.rgba
            }"
          ></span>
          <span class="color-label">{{c.hls}}</span>
        </div>
        <input type="text" v-model="colorMap[c.hex]">
      </div>
    </div>
    <codemirror
      v-model="cssString"
      :options="{
        mode: 'css',
        theme: 'material-darker',
        lineNumbers: true
      }"
    ></codemirror>
    <codemirror
      :value="themeCss"
      :options="{
        mode: 'css',
        theme: 'material-darker',
        lineNumbers: true,
        readOnly: true
      }"
    ></codemirror>
  </div>
</template>

<script lang="ts">
import { Component, Vue, Watch } from 'vue-property-decorator'
import colorString from 'color-string'
import postcss, { Root } from 'postcss'
import valueParser  from 'postcss-value-parser'
import prettier from 'prettier'
import parserPostcss from 'prettier/parser-postcss'
import * as cssWhat from 'css-what'
import cssString from './css-text'

import { codemirror } from 'vue-codemirror'
import 'codemirror/mode/css/css'
import 'codemirror/lib/codemirror.css'
import 'codemirror/theme/material-darker.css'

@Component({
  components: {codemirror},
})
export default class App extends Vue {
  private themeCss = ''
  private prefix = '.prefix'
  private colors = new Set<string>()
  private cssString = cssString
  private cssAst: Root | null = null
  private colorMap: {[index: string]: string} = {}

  private get colorGroups () {
    const sortedArray = Array.from(this.colors).sort()
    return sortedArray.map((d) => {
      const color = colorString.get(d)
      return {
        hls: d,
        rgba: colorString.to.rgb(color?.value || []),
        hex: colorString.to.hex(color?.value || [])
      }
    })
  }

  @Watch('cssString', { immediate: true })
  private cssStringChange() {
    try {
      this.colors =new Set()
      this.cssAst = postcss.parse(this.cssString)
      this.cssAst.walkDecls((decl) => {
        const value = decl.value
        const vueParseResult = valueParser(value)
        
        vueParseResult.walk((node) => {
          let subValue = node.value
          if (node.type === 'function') {
            subValue = valueParser.stringify(node)
          }
          const color = colorString.get(subValue)
          if (color) {
            this.colors.add(colorString.to.hex(color.value))
          }
        })
      })
    } catch (error) {/* 静默失败 */}
  }

  @Watch('prefix')
  @Watch('colorMap', { deep: true })
  @Watch('cssAst', { immediate: true })
  private cssAstChange() {
    try {
      if (!this.cssAst) {
        return
      }
      this.themeCss = ''
      const cssAst = this.cssAst.clone()
      cssAst.walkDecls((decl) => {
        let hasColor = false
        const value = decl.value
        const vueParseResult = valueParser(value)
        
        vueParseResult.walk((node) => {
          let subValue = node.value
          if (node.type === 'function') {
            subValue = valueParser.stringify(node)
          }
          const color = colorString.get(subValue)
          if (color) {
            hasColor = true
          }
        })
        if (!hasColor) {
          decl.remove()
        }
      })

      cssAst.walkRules((rule) => {
        if (!(rule.nodes && rule.nodes.length > 0)) {
          rule.remove()
        }
        try {
          const selectors = cssWhat.parse(rule.selector)
          const prefix = this.prefix ? this.prefix + ' ' : ''
          rule.selector = selectors
            .map((s) => prefix + cssWhat.stringify([s]))
            .join(',\n')
        } catch (error) {
          //
        }
      })

      cssAst.walkAtRules((atrule) => {
        if (
          !(atrule.nodes && atrule.nodes.length > 0) &&
          atrule.name !== 'charset'
        ) {
          atrule.remove()
        }
      })

      cssAst.walkDecls((decl) => {
        const value = decl.value
        const vueParseResult = valueParser(value)
        
        vueParseResult.walk((node) => {
          let subValue = node.value
          if (node.type === 'function') {
            subValue = valueParser.stringify(node)
          }
          const color = colorString.get(subValue)
          if (color) {
            const hex = colorString.to.hex(color.value)
            if (this.colorMap[hex]) {
              node.value = this.colorMap[hex]
              node.type = 'word'
            }
          }
        })

        decl.value = valueParser.stringify(vueParseResult.nodes)
      })

      postcss.stringify(cssAst, (str: string) => {
        this.themeCss += str
      })

      this.themeCss = prettier.format(this.themeCss, {
        parser: 'css',
        plugins: [parserPostcss],
      })
    } catch (error) {/* 静默失败 */}
  }
}
</script>

<style lang="less">
html,
body,
#app {
  width: 100%;
  height: 100%;
  margin: 0;
  background: #1e2433;
  display: flex;
  .side-bar {
    padding: 10px;
    width: 288px;
    color: white;
  }
  .vue-codemirror {
    display: block;
    background: transparent;
    border: none;
    height: 100%;
    color: white;
    padding: 0 10px;
    line-height: 24px;
    box-sizing: border-box;
    outline: none;
    flex-grow: 1;
    flex-basis: 50%;
    padding: 0;
    border-left: 1px solid #000000;
  }
  .CodeMirror {
    height: 100%;
  }

  label,
  input {
    margin: 10px 0;
  }

  .color-block {
    display: inline-block;
    width: 12px;
    height: 12px;
    vertical-align: middle;
  }

  .color-label {
    font-size: 12px;
    margin-left: 5px;
  }
}
</style>
